package org.papervision3d.core.render.data;

import nme.display.Graphics;

import org.papervision3d.core.clipping.draw.Clipping;
import org.papervision3d.core.clipping.draw.RectangleClipping;
import org.papervision3d.core.render.command.RenderableListItem;
import org.papervision3d.objects.DisplayObject3D;

using Lambda;

/**
* Quadrant tree for storing drawing primitives
*/
class QuadTree
{
	private var _root:QuadTreeNode;
	private var _clip:Clipping;
	private var _rect:RectangleClipping;
	private var _center:Array<RenderableListItem>;
	private var _result:Array<RenderableListItem>;
	private var _whatExcept:DisplayObject3D;
	private var _minX:Float;
	private var _minY:Float;
	private var _maxX:Float;
	private var _maxY:Float;
	private var _child:RenderableListItem;
	private var _children:Array<RenderableListItem>;
	private var i:Int;
	private var _maxlevel:Int;
	
	private function getList(node:QuadTreeNode):Void
	{
		_maxlevel = 4;
		
		
		if(node == null){
			return;
		}
		
		if (node.onlysourceFlag && _whatExcept == node.onlysource){
			return;
		}

		if (_minX < node.xdiv)
		{
			if (node.lefttopFlag && _minY < node.ydiv){
				getList(node.lefttop);
			}
			if (node.leftbottomFlag && _maxY > node.ydiv){
				getList(node.leftbottom);
			}
		}
		
		if (_maxX > node.xdiv)
		{
			if (node.righttopFlag && _minY < node.ydiv)
				getList(node.righttop);
			
			if (node.rightbottomFlag && _maxY > node.ydiv)
				getList(node.rightbottom);
			
		}
		
		_children = node.center;
		if (_children != null) {
			i = _children.length;
			while (i-- != 0)
			{
				_child = _children[i];
				if ((_whatExcept == null || _child.instance != _whatExcept) && _child.maxX > _minX && _child.minX < _maxX && _child.maxY > _minY && _child.minY < _maxY)
					_result.push(_child);
			}
		}           
	}
	
	private function getParent(node:QuadTreeNode = null):Void
	{
		if(node == null)
			return;
			
		node = node.parent;
		
		if (node == null || (node.onlysourceFlag && _whatExcept == node.onlysource))
			return;

		_children = node.center;
		if (_children != null) {
			i = _children.length;
			while (i-- != 0)
			{
				_child = _children[i];
				if ((_whatExcept == null || _child.instance != _whatExcept) && _child.maxX > _minX && _child.minX < _maxX && _child.maxY > _minY && _child.minY < _maxY)
					_result.push(_child);
			}
		}
		getParent(node);
	}
	
	/**
	 * Defines the clipping object to be used on the drawing primitives.
	 */
	public var clip(get_clip, set_clip):Clipping;
	private function get_clip():Clipping
	{
		return _clip;
	}
	
	private function set_clip(val:Clipping):Clipping
	{
		_clip = val;
		_rect = _clip.asRectangleClipping();
		if (_root == null)
			_root = new QuadTreeNode((_rect.minX + _rect.maxX)/2, (_rect.minY + _rect.maxY)/2, _rect.maxX - _rect.minX, _rect.maxY - _rect.minY, 0, null, _maxlevel);
		else
			_root.reset((_rect.minX + _rect.maxX) / 2, (_rect.minY + _rect.maxY) / 2, _rect.maxX - _rect.minX, _rect.maxY - _rect.minY, _maxlevel);	
		
		return _clip;
	}
	
	public var maxLevel(get_maxLevel, set_maxLevel):Int;
	private function get_maxLevel():Int{
		return _maxlevel;
	}
	
	private function set_maxLevel(value:Int):Int{
		_maxlevel = value;
		if(_root != null)
			_root.maxlevel = _maxlevel;
			
		return value;
	}
	
	/**
	 * @inheritDoc
	 */
	public function add(renderItem:RenderableListItem):Void
	{
		if (_clip.check(renderItem))
		{
			_root.push(renderItem);
		}
	}
	
	/**
	* removes a drawing primitive from the quadrant tree.
	* 
	* @param	pri	The drawing primitive to remove.
	*/
	public function remove(renderItem:RenderableListItem):Void
	{
		_center = renderItem.quadrant.center;
		_center.splice(_center.indexOf(renderItem), 1);
	}
	
	/**
	 * A list of primitives that have been clipped.
	 * 
	 * @return	An array containing the primitives to be rendered.
	 */
	public function list():Array<RenderableListItem>
	{
		_result = [];
				
		_minX = -1000000;
		_minY = -1000000;
		_maxX = 1000000;
		_maxY = 1000000;
		_whatExcept = null;
		
		getList(_root);
		
		return _result;
	}
	
	/**
	 * Returns an array containing all primiives overlapping the specifed primitive's quadrant.
	 * 
	 * @param	renderItem			The drawing primitive to check.
	 * @param	ex		[optional]	Excludes primitives that are children of the 3d object.
	 * @return						An array of drawing primitives.
	 */
	public function getOverlaps(renderItem:RenderableListItem, ex:DisplayObject3D = null):Array<RenderableListItem>
	{
		_result = [];
				
		_minX = renderItem.minX;
		_minY = renderItem.minY;
		_maxX = renderItem.maxX;
		_maxY = renderItem.maxY;
		_whatExcept = ex;
		
		getList(renderItem.quadrant);
		getParent(renderItem.quadrant);
		return _result;
	}
	
	/**
	* Calls the render function on all primitives in the quadrant tree
	*/
	public function render(renderSessionData:RenderSessionData, graphics:Graphics):Void
	{
		_root.render(Math.NEGATIVE_INFINITY, renderSessionData, graphics);
	}
	
	public function getRoot():QuadTreeNode{
		return _root;
	}
}

